package org.idea.qiyu.framework.sqlsession.factory.mapping.registrar;

import com.alibaba.druid.pool.DruidDataSource;
import org.idea.qiyu.framework.sqlsession.factory.mapping.properties.DatasourceProperties;
import org.idea.qiyu.framework.sqlsession.factory.mapping.properties.MultiSqlSessionProperties;
import org.idea.qiyu.framework.sqlsession.factory.mapping.utils.EnvironmentUtils;
import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 自定义的mybatis扫描注册器
 *
 * @Author linhao
 * @Date created in 10:07 下午 2021/5/26
 */
public class MyBatisScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    private static final Logger logger = LoggerFactory.getLogger(MyBatisScannerRegistrar.class);
    private ResourceLoader resourceLoader;
    private Environment environment;
    public static MultiSqlSessionProperties multiSqlSessionProperties;
    private static final String DEFAULT_DATASOURCE_PREFIX = "multi.sqlsession.factory.datasources";

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        try {
            this.initMultiSqlSessionPropertiesBeforeContextRefresh();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //在spring容器初始化之前已经加载好了配置
        Set<String> keys = multiSqlSessionProperties.getDatasources().keySet();
        for (String datasourceName : keys) {
            DatasourceProperties datasourceProperties = multiSqlSessionProperties.getDatasources().get(datasourceName);
            String sqlSessionFactoryRef = datasourceProperties.getFactoryName();
            ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
            // this check is needed in Spring 3.1
            if (resourceLoader != null) {
                scanner.setResourceLoader(resourceLoader);
            }
            scanner.setSqlSessionFactoryBeanName(sqlSessionFactoryRef);
            //MybatisMapperScan注解中的value字段值和basePackages字段值含义相同，所以这里有两个循环判断
            List<String> basePackages = new ArrayList<>();
            basePackages.add(datasourceProperties.getPackageName());
            scanner.registerFilters();
            scanner.doScan(StringUtils.toStringArray(basePackages));
        }
        try {
            this.initDatasource(registry);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 兼容多种数据源类型的初始化策略 todo
     *
     * @param beanDefinitionRegistry
     */
    public void initDatasource(BeanDefinitionRegistry beanDefinitionRegistry) {
        boolean hasPrimary = false;
        for (String datasourceName : multiSqlSessionProperties.getDatasources().keySet()) {
            DatasourceProperties datasourceProperties = multiSqlSessionProperties.getDatasources().get(datasourceName);
            BeanDefinitionBuilder datasourceBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DruidDataSource.class);

            datasourceBeanDefinitionBuilder.addPropertyValue("username",datasourceProperties.getUsername());
            datasourceBeanDefinitionBuilder.addPropertyValue("password",datasourceProperties.getPassword());
            datasourceBeanDefinitionBuilder.addPropertyValue("url",datasourceProperties.getUrl());

            datasourceBeanDefinitionBuilder.addPropertyValue("maxActive",9);
            datasourceBeanDefinitionBuilder.addPropertyValue("initialSize",3);
            datasourceBeanDefinitionBuilder.addPropertyValue("minIdle",1);
            datasourceBeanDefinitionBuilder.addPropertyValue("maxWait",30000);
            if(!hasPrimary){
                hasPrimary = true;
                datasourceBeanDefinitionBuilder.setPrimary(true);
            }
            BeanDefinition beanDefinition = datasourceBeanDefinitionBuilder.getBeanDefinition();
            beanDefinitionRegistry.registerBeanDefinition(datasourceName,beanDefinition);
            logger.info("{} registry success ",datasourceName);
        }
    }

    /**
     * 在容器初始化之前，提前读取配置文件中的属性值
     *
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private void initMultiSqlSessionPropertiesBeforeContextRefresh() throws InvocationTargetException, IllegalAccessException, IntrospectionException {
        multiSqlSessionProperties = new MultiSqlSessionProperties();
        Map<String, String> datasourcePropertiesMap = EnvironmentUtils.getSubProperties(environment, DEFAULT_DATASOURCE_PREFIX);
        Map<String, Map<String, String>> properties = new HashMap<>();
        for (String datasourcePropertiesKey : datasourcePropertiesMap.keySet()) {
            String datasourceName = datasourcePropertiesKey.substring(0, datasourcePropertiesKey.indexOf("."));
            String field = datasourcePropertiesKey.replaceAll(datasourceName + ".", "");
            Map<String, String> itemMaps = properties.get(datasourceName);
            if (itemMaps == null) {
                itemMaps = new HashMap<>();
            }
            itemMaps.put(field, datasourcePropertiesMap.get(datasourcePropertiesKey));
            properties.put(datasourceName, itemMaps);
        }
        for (String datasourceName : properties.keySet()) {
            Map<String,String> propertiesItemMap = properties.get(datasourceName);
            DatasourceProperties datasourceProperties = new DatasourceProperties();
            for (String field : propertiesItemMap.keySet()) {
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field, DatasourceProperties.class);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(datasourceProperties,propertiesItemMap.get(field));
            }
            multiSqlSessionProperties.getDatasources().put(datasourceName,datasourceProperties);
        }
    }

}
